linux网络协议栈(六)传输层 (2)UDP协议 1)创建和bind

您所在的位置:网站首页 linux udp传输 linux网络协议栈(六)传输层 (2)UDP协议 1)创建和bind

linux网络协议栈(六)传输层 (2)UDP协议 1)创建和bind

2023-03-27 18:31| 来源: 网络整理| 查看: 265

6.2.2.2、UDP协议详解:6.2.2.2.1、创建一个IP数据报式的UDP传输通道:

int fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);

对于UDP通讯,不论是服务器还是客户端都必须各自首先创建一个IP数据报式的UDP传输通道,这里不得不先理解socket系统调用在干什么:

上面是socket系统调用的实际内核态实现,首先调用函数sock_create创建一个socket结构体描述符,它的用途是在socket文件系统中把应用程序和即将创建的传输通道挂接起来,注意它本身并不描述这个传输通道的具体细节,传输通道的具体细节在函数sock_create中实际创建后由另创建的sock结构体描述符实际记录,socket描述符会挂接sock描述符;

传输通道在内核中以文件形式管理,sock_create函数的返回值就是代表这个传输通道的文件,所以实际上socket描述符的作用在于,把应用程序的进程描述符和代表这个传输通道的文件,通过socket文件系统联系起来,函数sock_map_fd就是做这个事情;socket系统调用之后,说明该进程已打开这样一个socket文件,实际就是创建了一个socket传输通道;

既然是文件,那么就有文件的打开、关闭、读写、阻塞访问等文件系统相关的内容,对于socket文件同样是通过fcntl系统调用去配置它的阻塞/非阻塞等文件属性,关闭文件也是使用close系统调用,只是读写文件不是使用read/write,而是recv族/send族系统调用。

至此,要明确:

1、             socket系统调用实际是在socket文件系统中创建并打开一个文件,内核已经记录了这件事情,文件的关闭和文件属性配置和其他文件系统无差异(close、fcntl系统调用),但读写文件不同(recv族/send族系统调用);在内核中以socket结构体描述符维护管理这个文件;

2、             文件创建的背后实际是传输通道的创建,传输通道的细节由sock_create函数中创建的sock结构体描述符记录,socket结构体描述符挂接sock结构体描述符;

下面再看传输通道的创建过程,进入函数sock_create,它继续调用函数__sock_create,它调用__sock_create函数,该函数有意义的实质性操作如下:

1、  调用sock =sock_alloc();分配socket结构体描述符,并赋值其type字段为socket类型(sock->type = type;);

2、  调用协议族的create方法(pf->create(net,sock, protocol);),对于AF_INET即IP协议族,调用inet_create函数,这里实际创建传输通道;

下面着重介绍下inet_create:

1、它首先需要确定套接字类型和传输层协议是什么,这就根据用户socket调用中传递的type参数和protocol参数,这就可以确定使用哪个套接字ops和传输层ops(即4.2.2.2中介绍过的inetsw_array数组成员);socket结构体描述符保存套接字ops,传输层ops由后面创建的sock结构体描述符保存;

2、然后创建sock结构体描述符,它实际管理传输通道,后面的IP描述符(inet_sock结构体)和传输层描述符(udp_sock/tcp_sock结构体)都由它一级级继承,具体地,先由sk_alloc函数分配一个sock结构体描述符,然后创建一个继承了sock描述符的inet_sock结构体的IP描述符(注意,这里有一个小技巧,在调用sk_alloc创建sock描述符时,已创建了IP描述符),IP描述符的作用是保存IP协议相关的内容,如ttl等IP协议的细节内容,以及bind的本地IP和端口和connect的目的IP和端口等重要内容,下图是inet_sock结构体;

3、然后要对sock描述符进行初始化,如下:

记录协议族(sk->sk_family= family),这里是AF_INET;

记录传输层ops(sk->sk_prot、sk->sk_prot_creator),这里UDP是udp_prot;这就标识了这个传输通道使用UDP通讯协议;

初始化传输通道的收发队列(sock->sk_receive_queue、sock->sk_write_queue),接收方向上,传输层只负责把收到的报文加入传输通道 sock的接收队列,由应用程序的recv族系统调用从sock接收队列中获取;发送方向上,传输层负责把报文加入传输通道sock的发送队列并发送到网络层;

对于TCP协议有意义,标识TCP协议状态;

把sock和socket挂接起来(socket->sk指向sock),并标识sock的套接字类型(sock->sk_type),然后重点是sock的sk_sleep等待队列,用于应用程序的recv族系统调用陷入内核后发现没有报文可以获取,如果该socket文件为阻塞访问,则应用程序被陷入等待直至超时,当传输层收到报文后会唤醒它,如下:

当sock的状态发生变化或者收到报文,将唤醒正在等待报文的应用程序;

还要在sock中记录传输层协议类型(protocol),以及把接收到的报文放入sock接收队列的方法(sk->sk_backlog_rcv,对应UDP的处理函数是__udp_queue_rcv_skb);

至此,要明确:

1、         函数sock_create实际分配socket描述符,并根据协议族类型(socket系统调用的family参数)调用不同协议族的create方法,对于IPV4(AF_INET)为函数inet_create,它实际创建并初始化传输通道,传输通道由sock描述符描述;

2、         sock描述符根据套接字类型、传输层协议类型定义了套接字ops和传输层ops,它们包括了传输层全部需要的方法的处理函数,此外初始化了包括sock收发队列、报文接收的阻塞/唤醒机制等一系列有意义的初始化

3、         在报文接收方向上,传输层只负责把收到的报文加入传输通道 sock的接收队列,由应用程序的recv族系统调用从sock接收队列中获取;发送方向上,传输层负责把报文加入传输通道sock的发送队列并发送到网络层;

4、          sock描述符的分配实际是inet_sock描述符的分配,后者继承前者,inet_sock描述符保存IP协议相关的内容,如ttl等IP协议的细节内容,以及bind的本地IP和端口和connect的目的IP和端口等重要内容;

上述描述符们还有一些其他的重要信息,后续会逐渐发现;

6.2.2.2.2、bind本地地址:首先需要理解bind到底是干什么用的,下图是一个UDP报文接收的一个详细过程图:

服务器在创建了传输通道后,需要告诉内核一个能标识该传输通道sock的东西,让内核传输层以后在收到任何一个报文后,能够根据报文中的信息知道是该放入哪个传输通道sock,这样应用程序才能从对应的传输通道sock中获取报文;

创建这个能标识传输通道sock的东西,就是bind操作,每个应用程序通过bind一个地址,这个地址包括能标识这个主机的IP地址和能标识该应用程序的端口号,bind将在内核中相应的表中创建相应的表项,这个表把不同应用程序的传输通道sock和它给出的地址的映射关系记录在一个个表项之中,这样在收到报文后,根据报文的源目IP和源目端口,在这个表中查找是否有匹配的表项,进而找到对应的传输通道sock,这样就最终知道了这个报文应该放入哪个传输通道,进而被哪个应用程序接收;

对于UDP协议,这个表是全局变量udptable,不同传输层协议的绑定表不同,但都定义在传输层ops的h字段之中,绑定表都是hash表的方式。

下面详细介绍bind:

根据文件描述符从socket文件系统找到对应的socket描述符,然后同样是根据套接字类型,调用ops的bind方法,对于ipv4协议族的数据报套接字,其bind方法是inet_bind函数,下面着重介绍inet_bind函数;

1、bind其实是把源IP和源端口记录在IP描述符(inet_sock)中,其中源IP记录在其rcv_addr和saddr字段,源端口记录在num和sport字段,raw型套接字由于没有端口的概念,所以它独立bind源IP即可;另外注意0-1023的端口号只有超级用户权限的进程才能使用;

2、对于组播IP和广播IP,它们不能绑定,这类连接的绑定是绑定接口,而不是绑定IP地址;

3、对于UDP和TCP,端口绑定之前,需要用其传输层的get_port方法判断端口是否可以绑定,有时应用程序没有指定端口是多少,即提供给内核的端口号为0,这时get_port方法会为应用程序随机选取一个当前可用端口,

传输层ops的get_port方法,对于UDP是函数 udp_v4_get_port,对于TCP是函数inet_csk_get_port,用途就是检测该端口号是否可以被绑定,udp_v4_get_port把传输通道sock以一个hash表项的形式加入UDP协议的绑定表udptable中,其hash索引值就是最终要绑定的端口号,并把这个端口号记录在IP描述符中(inet_sock->num),如下图:

在UDP绑定表中成功绑定端口后,还要把绑定的端口号赋值给IP描述符的sport字段,然后还要绑定源IP,就是把源IP赋值给IP描述符的saddr和rcv_addr字段,如下图:

最后还要复位sock的路由缓存(sk_dst_reset(sk)),至此bind操作完毕,要明确:

1、 bind的意义在于让传输层收到报文后,知道这个报文是给哪个传输通道sock;

2、 bnid的内容包括IP地址和端口号,分别用于标识所在主机和哪一个应用程序;

3、 对于UDP、TCP、RAW型套接字的传输层ops,有各自的hash表用于存储绑定关系,都在各自的传输层ops的h字段中,对于raw型套接字,由于没有端口的概念,所以它直接绑定源IP即可,对于TCP和UDP,必须在绑定之前判断要绑定的端口号是否合法;当应用程序没有指定明确的端口号时(端口号为0),内核会随机找一个当前可用的端口号为其绑定;

4、 除了把传输通道sock绑定在绑定表外,还要把源IP、源端口记录在IP描述符中(源IP记录在IP描述符的saddr和rcv_addr字段,源端口记录在IP描述符的num和sport字段);

5、  对于组播和广播IP地址,不能绑定IP,只能绑定接口,但同样可以绑定端口;

为能更好的理解bind的作用,下面简单描述下UDP协议报文的接收过程:

1、            已经知道它从udp_rcv开始,它将调用函数__udp4_lib_rcv,它首先根据IP协议头和UDP协议头取得源目IP和源目端口;

2、            然后调用函数__udp4_lib_lookup_skb在UDP协议的绑定表中,以源目IP、源目端口、入接口索引号等信息,查找对应的传输通道sock,查找细节暂略(查找函数为__udp4_lib_lookup,查找细节很复杂,不是普通的hash查找,而是一种更智能快速的方式),

3、            这里注意,组播/广播UDP的处理不走这一流程(它调用函数__udp4_lib_mcast_deliver处理),因为组播/广播IP并没有绑定在绑定表中;

4、            若找到传输通道sock,则把skb放入该sock的接收队列(sock->sk_receive_queue),并唤醒正在睡眠等待的用户接收进程;

5、             若找不到传输通道sock,返回"不可达"(ICMP_DEST_UNREACH),并且是"端口不可达"(ICMP_PORT_UNREACH);

可见,只有bind之后,应用程序才能按自己bind的地址去接收报文,否则内核无法给其传输通道转送报文; 0 0 linux网络协议栈(六)传输层 (2)UDP协议 1)创建和bind linux网络协议栈(六)传输层 (3)UDP协议 5)传输层框架小节 linux网络协议栈(六)传输层 (2)UDP协议 2)connect linux网络协议栈(六)传输层 (3)UDP协议 3)报文发送 send/sendto/sendmsg linux网络协议栈(六)传输层 (3)UDP协议 4)报文接收 recv/recvfrom/recvmsg linux网络协议栈(六)传输层 (1)传输层基本框架 传输层(2)-UDP协议 传输层-1、UDP协议 网络层协议和传输层协议 (传输层)UDP协议 (传输层)UDP协议 (传输层)UDP协议 (传输层)UDP协议 (传输层)UDP协议 传输层UDP协议 传输层:UDP协议 UNIX网络编程1:传输层协议TCP、UDP、SCTP Linux内核--网络栈实现分析(五)--传输层之UDP协议(上) gradle工程配置 设计模式——迭代器模式 zen-Coding的使用 终于将windows search 彻底卸载 出现connection9060:0: detected conn error信息时的问题定位分析 linux网络协议栈(六)传输层 (2)UDP协议 1)创建和bind matlab中的反正切函数 正则表达式之最短匹配 验证一个正整数各各位上是否有指定的数字 42 Android fragmentManager 获取fragment Android中Bitmap、Drawable、byte[]转换 Access2003和2007/2010中这样执行SQL语句 cocos2dx 官方示例学习(二), Vector pOJ 2965 The Pilots Brothers' refrigerator


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3